/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package utilities;

import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import static java.lang.Math.*;
/**
 *
 * @author ctuchsch
 */
public class EulerCamera extends CommonCamera
{

    public EulerCamera(Builder b)
    {
        super(b.aspectRatio, b.x, b.y, b.z, b.pitch, b.yaw, b.roll, b.zNear, b.zFar, b.fov);
    }

    /**
     * Processes mouse input and converts it in to camera movement.
     */
    @Override
    public void processMouse()
    {
        final float MAX_LOOK_UP = 90;
        final float MAX_LOOK_DOWN = -90;
        float mouseDX = Mouse.getDX() * 0.16f;
        float mouseDY = Mouse.getDY() * 0.16f;
        if (yaw() + mouseDX >= 360)
            setYaw(yaw() + mouseDX - 360);
        else if (yaw() + mouseDX < 0)
            setYaw(360 - yaw() + mouseDX);
        else
            setYaw(yaw() + mouseDX);
        if (pitch() - mouseDY >= MAX_LOOK_DOWN
                && pitch() - mouseDY <= MAX_LOOK_UP)
            setPitch(pitch() + -mouseDY);
        else if (pitch() - mouseDY < MAX_LOOK_DOWN)
            setPitch(MAX_LOOK_DOWN);
        else if (pitch() - mouseDY > MAX_LOOK_UP)
            setPitch(MAX_LOOK_UP);
    }

    /**
     * Processes mouse input and converts it in to camera movement.
     *
     * @param mouseSpeed the speed (sensitivity) of the mouse, 1.0 should
     * suffice
     */
    @Override
    public void processMouse(float mouseSpeed)
    {
        final float MAX_LOOK_UP = 90;
        final float MAX_LOOK_DOWN = -90;
        float mouseDX = Mouse.getDX() * mouseSpeed * 0.16f;
        float mouseDY = Mouse.getDY() * mouseSpeed * 0.16f;
        if (yaw() + mouseDX >= 360)
            setYaw(yaw() + mouseDX - 360);
        else if (yaw() + mouseDX < 0)
            setYaw(360 - yaw() + mouseDX);
        else
            setYaw(yaw() + mouseDX);
        if (pitch() - mouseDY >= MAX_LOOK_DOWN
                && pitch() - mouseDY <= MAX_LOOK_UP)
            setPitch(pitch() + -mouseDY);
        else if (pitch() - mouseDY < MAX_LOOK_DOWN)
            setPitch(MAX_LOOK_DOWN);
        else if (pitch() - mouseDY > MAX_LOOK_UP)
            setPitch(MAX_LOOK_UP);
    }

    /**
     * Processes mouse input and converts it into camera movement.
     *
     * @param mouseSpeed the speed (sensitivity) of the mouse, 1.0 should
     * suffice
     * @param maxLookUp the maximum angle in degrees at which you can look up
     * @param maxLookDown the maximum angle in degrees at which you can look
     * down
     */
    @Override
    public void processMouse(float mouseSpeed, float MAX_LOOK_UP, float MAX_LOOK_DOWN)
    {
        float mouseDX = Mouse.getDX() * mouseSpeed * 0.16f;
        float mouseDY = Mouse.getDY() * mouseSpeed * 0.16f;
        if (yaw() + mouseDX >= 360)
            setYaw(yaw() + mouseDX - 360);
        else if (yaw() + mouseDX < 0)
            setYaw(360 - yaw() + mouseDX);
        else
            setYaw(yaw() + mouseDX);
        
        if (pitch() - mouseDY >= MAX_LOOK_DOWN
                && pitch() - mouseDY <= MAX_LOOK_UP)
            setPitch(pitch() + -mouseDY);
        else if (pitch() - mouseDY < MAX_LOOK_DOWN)
            setPitch(MAX_LOOK_DOWN);
        else if (pitch() - mouseDY > MAX_LOOK_UP)
            setPitch(MAX_LOOK_UP);
    }

    /**
     * Processes keyboard input and converts into camera movement.
     *
     * @param delta the elapsed time since the last frame update in milliseconds
     * @throws IllegalArgumentException if delta is 0 or delta is smaller than 0
     */
    @Override
    public void processKeyboard(float delta)
    {
        if (delta <= 0)
            throw new IllegalArgumentException("delta " + delta + " is 0 or is smaller than 0");

        boolean keyUp = Keyboard.isKeyDown(Keyboard.KEY_UP) || Keyboard.isKeyDown(Keyboard.KEY_W);
        boolean keyDown = Keyboard.isKeyDown(Keyboard.KEY_DOWN) || Keyboard.isKeyDown(Keyboard.KEY_S);
        boolean keyLeft = Keyboard.isKeyDown(Keyboard.KEY_LEFT) || Keyboard.isKeyDown(Keyboard.KEY_A);
        boolean keyRight = Keyboard.isKeyDown(Keyboard.KEY_RIGHT) || Keyboard.isKeyDown(Keyboard.KEY_D);
        boolean flyUp = Keyboard.isKeyDown(Keyboard.KEY_SPACE);
        boolean flyDown = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT);

        if (keyUp && keyRight && !keyLeft && !keyDown)
            moveFromLook(delta * 0.003f, 0, -delta * 0.003f);
        if (keyUp && keyLeft && !keyRight && !keyDown)
            moveFromLook(-delta * 0.003f, 0, -delta * 0.003f);
        if (keyUp && !keyLeft && !keyRight && !keyDown)
            moveFromLook(0, 0, -delta * 0.003f);
        if (keyDown && keyLeft && !keyRight && !keyUp)
            moveFromLook(-delta * 0.003f, 0, delta * 0.003f);
        if (keyDown && keyRight && !keyLeft && !keyUp)
            moveFromLook(delta * 0.003f, 0, delta * 0.003f);
        if (keyDown && !keyUp && !keyLeft && !keyRight)
            moveFromLook(0, 0, delta * 0.003f);
        if (keyLeft && !keyRight && !keyUp && !keyDown)
            moveFromLook(-delta * 0.003f, 0, 0);
        if (keyRight && !keyLeft && !keyUp && !keyDown)
            moveFromLook(delta * 0.003f, 0, 0);
        if (flyUp && !flyDown)
            setY(y() + delta * 0.003f);
        if (flyDown && !flyUp)
            setY(y() - delta * 0.003f);
    }

    /**
     * Processes keyboard input and converts into camera movement.
     *
     * @param delta the elapsed time since the last frame update in milliseconds
     * @param speed the speed of the movement (normal = 1.0)
     * @throws IllegalArgumentException if delta is 0 or delta is smaller than 0
     */
    @Override
    public void processKeyboard(float delta, float speed)
    {
        if (delta <= 0)
            throw new IllegalArgumentException("delta " + delta + " is 0 or is smaller than 0");

        boolean keyUp = Keyboard.isKeyDown(Keyboard.KEY_UP) || Keyboard.isKeyDown(Keyboard.KEY_W);
        boolean keyDown = Keyboard.isKeyDown(Keyboard.KEY_DOWN) || Keyboard.isKeyDown(Keyboard.KEY_S);
        boolean keyLeft = Keyboard.isKeyDown(Keyboard.KEY_LEFT) || Keyboard.isKeyDown(Keyboard.KEY_A);
        boolean keyRight = Keyboard.isKeyDown(Keyboard.KEY_RIGHT) || Keyboard.isKeyDown(Keyboard.KEY_D);
        boolean flyUp = Keyboard.isKeyDown(Keyboard.KEY_SPACE);
        boolean flyDown = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT);

        if (keyUp && keyRight && !keyLeft && !keyDown)
            moveFromLook(speed * delta * 0.003f, 0, -speed * delta * 0.003f);
        if (keyUp && keyLeft && !keyRight && !keyDown)
            moveFromLook(-speed * delta * 0.003f, 0, -speed * delta * 0.003f);
        if (keyUp && !keyLeft && !keyRight && !keyDown)
            moveFromLook(0, 0, -speed * delta * 0.003f);
        if (keyDown && keyLeft && !keyRight && !keyUp)
            moveFromLook(-speed * delta * 0.003f, 0, speed * delta * 0.003f);
        if (keyDown && keyRight && !keyLeft && !keyUp)
            moveFromLook(speed * delta * 0.003f, 0, speed * delta * 0.003f);
        if (keyDown && !keyUp && !keyLeft && !keyRight)
            moveFromLook(0, 0, speed * delta * 0.003f);
        if (keyLeft && !keyRight && !keyUp && !keyDown)
            moveFromLook(-speed * delta * 0.003f, 0, 0);
        if (keyRight && !keyLeft && !keyUp && !keyDown)
            moveFromLook(speed * delta * 0.003f, 0, 0);
        if (flyUp && !flyDown)
            setY(y() + speed * delta * 0.003f);
        if (flyDown && !flyUp)
            setY(y() - speed * delta * 0.003f);
    }

    /**
     * Processes keyboard input and converts into camera movement.
     *
     * @param delta the elapsed time since the last frame update in milliseconds
     * @param speedX the speed of the movement on the x-axis (normal = 1.0)
     * @param speedY the speed of the movement on the y-axis (normal = 1.0)
     * @param speedZ the speed of the movement on the z-axis (normal = 1.0)
     * @throws IllegalArgumentException if delta is 0 or delta is smaller than 0
     */
    @Override
    public void processKeyboard(float delta, float speedX, float speedY, float speedZ)
    {
        if (delta <= 0)
            throw new IllegalArgumentException("delta " + delta + " is 0 or is smaller than 0");

        boolean keyUp = Keyboard.isKeyDown(Keyboard.KEY_UP) || Keyboard.isKeyDown(Keyboard.KEY_W);
        boolean keyDown = Keyboard.isKeyDown(Keyboard.KEY_DOWN) || Keyboard.isKeyDown(Keyboard.KEY_S);
        boolean keyLeft = Keyboard.isKeyDown(Keyboard.KEY_LEFT) || Keyboard.isKeyDown(Keyboard.KEY_A);
        boolean keyRight = Keyboard.isKeyDown(Keyboard.KEY_RIGHT) || Keyboard.isKeyDown(Keyboard.KEY_D);
        boolean flyUp = Keyboard.isKeyDown(Keyboard.KEY_SPACE);
        boolean flyDown = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT);

        if (keyUp && keyRight && !keyLeft && !keyDown)
            moveFromLook(speedX * delta * 0.003f, 0, -speedZ * delta * 0.003f);
        if (keyUp && keyLeft && !keyRight && !keyDown)
            moveFromLook(-speedX * delta * 0.003f, 0, -speedZ * delta * 0.003f);
        if (keyUp && !keyLeft && !keyRight && !keyDown)
            moveFromLook(0, 0, -speedZ * delta * 0.003f);
        if (keyDown && keyLeft && !keyRight && !keyUp)
            moveFromLook(-speedX * delta * 0.003f, 0, speedZ * delta * 0.003f);
        if (keyDown && keyRight && !keyLeft && !keyUp)
            moveFromLook(speedX * delta * 0.003f, 0, speedZ * delta * 0.003f);
        if (keyDown && !keyUp && !keyLeft && !keyRight)
            moveFromLook(0, 0, speedZ * delta * 0.003f);
        if (keyLeft && !keyRight && !keyUp && !keyDown)
            moveFromLook(-speedX * delta * 0.003f, 0, 0);
        if (keyRight && !keyLeft && !keyUp && !keyDown)
            moveFromLook(speedX * delta * 0.003f, 0, 0);
        if (flyUp && !flyDown)
            setY(y() - speedY * delta * 0.003f);
        if (flyDown && !flyUp)
            setY(y() + speedY * delta * 0.003f);
    }

    /**
     * Move in the direction you're looking. That is, this method assumes a new
     * coordinate system where the axis you're looking down is the z-axis, the
     * axis to your left is the x-axis, and the upward axis is the y-axis.
     *
     * @param dx the movement along the x-axis
     * @param dy the movement along the y-axis
     * @param dz the movement along the z-axis
     */
    @Override
    public void moveFromLook(float dx, float dy, float dz)
    {
        //90 yaw   0pitch               0                                           .048
        //0,0,-.048            //0                                          .0  when not yawed x delta 0 for W key
        setX((float)(x() + (dx * (float) sin(toRadians(yaw() - 90)) + dz * sin(toRadians(yaw())))));
        //90yaw     0pitch                   0                                       
        //                             0                                      .048  when looking straight down W moves -Y axis
        setY((float)(y() - (dy * (float) sin(toRadians(pitch() - 90)) + dz * sin(toRadians(pitch())))));            
        //                                     0                              .048
        setZ((float)(z() - (dx * (float) cos(toRadians(yaw() - 90)) + dz * cos(toRadians(yaw())))));                
//        setZ(z() - dz);
//        setX(x() - dx);
//        setY(y() - dy);
    }
    /**
     * A builder helper class for the EulerCamera class.
     */
    public static class Builder
    {

        private float aspectRatio = 1;
        private float x = 0,
                y = 0,
                z = 0,
                pitch = 0,
                yaw = 0,
                roll = 0;
        private float zNear = 0.3f;
        private float zFar = 100;
        private float fov = 90;

        public Builder()
        {
        }

        /**
         * Sets the aspect ratio of the camera.
         *
         * @param aspectRatio the aspect ratio of the camera (window width /
         * window height)
         * @return this
         */
        public Builder setAspectRatio(float aspectRatio)
        {
            if (aspectRatio <= 0)
                throw new IllegalArgumentException("aspectRatio " + aspectRatio + " was 0 or was smaller than 0");
            this.aspectRatio = aspectRatio;
            return this;
        }

        /**
         * Sets the distance from the camera to the near clipping pane.
         *
         * @param nearClippingPane the distance from the camera to the near
         * clipping pane
         * @return this
         * @throws IllegalArgumentException if nearClippingPane is 0 or less
         */
        public Builder setNearClippingPane(float nearClippingPane)
        {
            if (nearClippingPane <= 0)
                throw new IllegalArgumentException("nearClippingPane " + nearClippingPane + " is 0 or less");
            this.zNear = nearClippingPane;
            return this;
        }

        /**
         * Sets the distance from the camera to the far clipping pane.
         *
         * @param farClippingPane the distance from the camera to the far
         * clipping pane
         * @return this
         * @throws IllegalArgumentException if farClippingPane is 0 or less
         */
        public Builder setFarClippingPane(float farClippingPane)
        {
            if (farClippingPane <= 0)
                throw new IllegalArgumentException("farClippingPane " + farClippingPane + " is 0 or less");
            this.zFar = farClippingPane;
            return this;
        }

        /**
         * Sets the field of view angle in degrees in the y direction.
         *
         * @param fov the field of view angle in degrees in the y direction
         * @return this
         */
        public Builder setFieldOfView(float fov)
        {
            this.fov = fov;
            return this;
        }

        /**
         * Sets the position of the camera.
         *
         * @param x the x-coordinate of the camera
         * @param y the y-coordinate of the camera
         * @param z the z-coordinate of the camera
         * @return this
         */
        public Builder setPosition(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
            return this;
        }

        /**
         * Sets the rotation of the camera.
         *
         * @param pitch the rotation around the x-axis in degrees
         * @param yaw the rotation around the y-axis in degrees
         * @param roll the rotation around the z-axis in degrees
         */
        public Builder setRotation(float pitch, float yaw, float roll)
        {
            this.pitch = pitch;
            this.yaw = yaw;
            this.roll = roll;
            return this;
        }

        /**
         * Constructs an instance of EulerCamera from this builder helper class.
         *
         * @return an instance of EulerCamera
         * @throws IllegalArgumentException if farClippingPane is the same or
         * less than nearClippingPane
         */
        public EulerCamera build()
        {
            if (zFar <= zNear)
                throw new IllegalArgumentException("farClippingPane " + zFar + " is the same or less than nearClippingPane " + zNear);
            return new EulerCamera(this);
        }
    }
}
